﻿using Microsoft.Extensions.Caching.Distributed;
using StackExchange.Redis;
using System;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace Apex.ASPNETCore
{
    /// <summary>
    /// 分布式缓存帮助类，实现了 IDistributedCacheHelper 接口
    /// </summary>
    public class DistributedCacheHelper : IDistributedCacheHelper
    {
        private readonly IDistributedCache distCache;

        /// <summary>
        /// 构造函数，初始化分布式缓存对象
        /// </summary>
        /// <param name="distCache">分布式缓存对象</param>
        public DistributedCacheHelper(IDistributedCache distCache)
        {
            this.distCache = distCache;
        }

        /// <summary>
        /// 创建缓存项的过期选项：绝对过期时间
        /// </summary>
        /// <param name="baseExpireSeconds">基础过期时间（秒）</param>
        /// <returns>分布式缓存项的过期选项</returns>
        private static DistributedCacheEntryOptions CreateOptions(int baseExpireSeconds)
        {
            // 使用随机数生成过期时间，这样可以避免短时间内同样的请求集中过期，而导致缓存雪崩问题
            // 过期时间.Random.Shared 是.NET6新增的
            double sec = Random.Shared.NextDouble(baseExpireSeconds, baseExpireSeconds * 2);
            // 将秒数转换为 TimeSpan，表示缓存项的过期时间
            TimeSpan expiration = TimeSpan.FromSeconds(sec);
            // 创建分布式缓存项的过期选项
            DistributedCacheEntryOptions options = new DistributedCacheEntryOptions();
            // 设置缓存项的绝对过期时间为当前时间加上计算得到的过期时间
            options.AbsoluteExpirationRelativeToNow = expiration;
            return options;
        }

        /// <summary>
        /// 获取缓存项，如果缓存中不存在则调用指定的委托进行创建
        /// </summary>
        /// <typeparam name="TResult">缓存项的类型</typeparam>
        /// <param name="cacheKey">缓存键</param>
        /// <param name="valueFactory">创建缓存项的委托</param>
        /// <param name="expireSeconds">缓存过期时间（秒）</param>
        /// <returns>缓存项的值</returns>
        public TResult? GetOrCreate<TResult>(string cacheKey, Func<DistributedCacheEntryOptions, TResult?> valueFactory, int expireSeconds = 60)
        {
            string jsonStr = distCache.GetString(cacheKey);
            // 如果缓存中不存在，则创建缓存项
            if (string.IsNullOrEmpty(jsonStr))
            {
                // 创建缓存项的过期选项：绝对过期时间
                var options = CreateOptions(expireSeconds);
                // 调用指定的委托创建缓存项的值：调用的时候配置滑动过期时间
                TResult? result = valueFactory(options);
                // 将缓存项序列化为 JSON 字符串
                string jsonOfResult = JsonSerializer.Serialize(result, typeof(TResult));
                // 将 JSON 字符串保存到缓存中，并设置绝对过期时间
                distCache.SetString(cacheKey, jsonOfResult, options);
                return result;
            }
            else
            {
                /*
                    "null"会被反序列化为null
                    TResult如果是引用类型，就有为null的可能性；
                    如果TResult是值类型,在写入的时候肯定写入的是0、1之类的值，反序列化出来不会是null
                    所以如果obj这里为null，那么存进去的时候一定是引用类型
                 */
                // 如果缓存中存在，则刷新缓存过期时间并返回缓存项的值
                distCache.Refresh(cacheKey);
                // 将 JSON 字符串反序列化为缓存项的值
                return JsonSerializer.Deserialize<TResult>(jsonStr)!;
            }
        }

        /// <summary>
        /// 异步方式获取缓存项，如果缓存中不存在则调用指定的委托进行创建
        /// </summary>
        /// <typeparam name="TResult">缓存项的类型</typeparam>
        /// <param name="cacheKey">缓存键</param>
        /// <param name="valueFactory">创建缓存项的委托</param>
        /// <param name="expireSeconds">缓存过期时间（秒）</param>
        /// <returns>缓存项的值（异步）</returns>
        public async Task<TResult?> GetOrCreateAsync<TResult>(string cacheKey, Func<DistributedCacheEntryOptions, Task<TResult?>> valueFactory, int expireSeconds = 60)
        {
            // 异步方式从缓存中获取以 JSON 格式存储的缓存项
            string jsonStr = await distCache.GetStringAsync(cacheKey);
            // 如果缓存中不存在，则创建缓存项
            if (string.IsNullOrEmpty(jsonStr))
            {
                // 创建缓存项的过期选项：绝对过期时间
                var options = CreateOptions(expireSeconds);
                // 调用指定的委托创建缓存项的值（异步）：调用的时候配置滑动过期时间
                TResult? result = await valueFactory(options);
                // 将缓存项序列化为 JSON 字符串
                string jsonOfResult = JsonSerializer.Serialize(result, typeof(TResult));
                // 异步方式将 JSON 字符串保存到缓存中，并设置绝对过期时间
                await distCache.SetStringAsync(cacheKey, jsonOfResult, options);
                return result;
            }
            else
            {
                // 如果缓存中存在，则刷新缓存过期时间并返回缓存项的值
                await distCache.RefreshAsync(cacheKey);
                // 将 JSON 字符串反序列化为缓存项的值
                return JsonSerializer.Deserialize<TResult>(jsonStr)!;
            }
        }

        /// <summary>
        /// 移除指定键的缓存项
        /// </summary>
        /// <param name="cacheKey">缓存键</param>
        public void Remove(string cacheKey)
        {
            distCache.Remove(cacheKey);
        }

        /// <summary>
        /// 异步方式移除指定键的缓存项
        /// </summary>
        /// <param name="cacheKey">缓存键</param>
        /// <returns>表示异步移除操作的任务</returns>
        public Task RemoveAsync(string cacheKey)
        {
            return distCache.RemoveAsync(cacheKey);
        }
    }
}
